#include "StdAfx.h"
#include <cmath>
#include <iostream>
#include <fstream>
#include <algorithm>


#include "GSpectForm.h"
#include "FFTWindow_cos2.h"
#include "lib/fftw++.h"

#include "utilities.h"


#pragma warning( disable : 4244 )


using namespace std;


#define MAX_FREQ 100
const double componentDef[10]={0.3,0.3,4.0,4.0,8.0,8.0,14.0,14.0,30.0,30.0};


struct threadInvoke{
	GSpectForm* p;
	int n;
	int start;
	int end;
};

UINT FFT_Working_Thread( LPVOID pParam ){
	threadInvoke *ti=(threadInvoke* )pParam;
	return ti->p->DoCalc(ti->n,ti->start,ti->end);
}



GSpectForm::~GSpectForm(void)
{
	//SAFE_DELETE(in);
	//SAFE_DELETE(out);
	for(size_t i=0;i<out.size();i++)
	{
		FFTWdelete(out[i].out);
		SAFE_DELETE(out[i].abs);
	}
	for(size_t i=0;i<bmpBuf.size();i++)
	{
		//FFTWdelete(out[i].out);
		SAFE_DELETE(bmpBuf[i].bmp);
	}
//	_CrtDumpMemoryLeaks();

}

void GSpectForm::Draw(CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid)
{
	Rect rc(rcBounds.left,rcBounds.top,rcBounds.right,rcBounds.bottom);
	Bitmap bmp(rcBounds.right,rcBounds.bottom);
	Graphics graphics(pdc->GetSafeHdc()); 

	FontFamily  fontFamily(L"Times New Roman");
	Font        font(&fontFamily, 12, FontStyleRegular, UnitPixel);
	SolidBrush  solidBrush(Color(255, 255, 255, 255));


	// Create a Graphics object that is associated with the image.
	Graphics* graph = Graphics::FromImage(&bmp);
	if(out.size())
	{
		if(viewType==false || selectedChannel==-1){
			DrawAllOutput(bmp,rcBounds.right,rcBounds.bottom);
		}else{
			//Bitmap bmp(rcBounds.right,rcBounds.bottom);
			DrawBitmap(selectedChannel,bmp,rcBounds.right,rcBounds.bottom);
		}

	}else{
		graph->DrawString(L"Data is NOT available!",-1,&font,PointF(0.0,0.0),&solidBrush);
	}

	// Draw the altered image.
	graphics.DrawImage(&bmp,rcBounds.left,rcBounds.top,rcBounds.right,rcBounds.bottom);
	SAFE_DELETE(graph);

}

UINT GSpectForm::DoCalc(int n,int start,int end)
{

	const Channel &chData=dataStore->GetChannel(n);
	/*int r;
	double tmp=log10((double)data.size())/log10(2.0);
	r=(abs(tmp-int(tmp))<1e-9)?(int)tmp:(int)tmp+1;
	int m=(int)pow(2.0,r);*/

	for(unsigned int i=0;i<out.size();i++)
	{
		if(out[i].channel==n)
			return -1;
	}
	if(start==0 && end==0)
	{
		start=0;
		end=chData.data.size();
	}
	//unsigned int S=data.size();
	unsigned int S=end-start;
	unsigned int np=S/2+1;//output size
	double *f=FFTWdouble(S);
	std::complex<double> *sout=FFTWComplex(np);
	if(!(f && sout))
		return -1;
	rcfft1d Forward(S,f,sout);
	//crfft1d Backward(n,g,f);
	FFTWindow *win = new FFTWindow_cos2(S,0.1,0.1);
	for(int i=start; i < end; i++) {
		dataStore->GetRefData(n,i,f[i-start]);
		f[i-start]=win->Filter(f[i-start]);
	}//copy

	Forward.fft(f,sout);

	//	FFT(in,out,r);
#ifdef _DEBUG
	ofstream ofs("fft.txt");

	/*for(int i=0;i<m;i++)
	ofs << out[i].real() <<"   " << out[i].imag() << endl;
	ofs<<endl<<endl;*/
	for(unsigned int i=0;i<np;i++)
		ofs<<std::abs(sout[i])<<endl;
	ofs.close();
#endif

	FFTOutput fo(n,dataStore->GetChannel(n).electrodeID,sout,start,end);
	fo.abs=new double[np];
	double mean=0.0;
	double max=0.0;
	fo.abs[0]=std::abs(fo.out[0]);
	for(unsigned int i=1;i<np;i++){
		fo.abs[i]=std::abs(fo.out[i]);
		max=fo.abs[i]>max?fo.abs[i]:max;
		mean+=fo.abs[i];
	}
	fo.max=max;
	fo.mean=mean/np;
	out.push_back(fo);
	FFTWdelete(f);
	//Sleep(5000);
	WBH_Close(wb);


	return out.size();
}

bool GSpectForm::StartCalc(int n,int start,int end)
{
	if(dataStore->GetChannelCount()<=n)
		return false;
	if(start>end || dataStore->GetChannel(n).data.size()<end)
		return false;
	threadInvoke ti;
	ti.n=n;
	ti.p=this;
	ti.start=start;
	ti.end=end;

	wb.SetDefaultPrompt(L"Calculating FFT...");
	CWinThread *thread=AfxBeginThread(FFT_Working_Thread,(void*)&ti);
	if(!thread)
		return false;

	wb.DoModal();//show wb
	//DWORD ret=-1;
	//GetExitCodeThread(thread->m_hThread,&ret);

	//return (ret==-1)?false:true;


	if(out[out.size()-1].channel!=n){
		return false;
	}

	//UpdateBitmap(n);
	return true;

}

void GSpectForm::DrawBitmap(int ch,Bitmap &bmp,int w,int h)
{
	unsigned int i=FindInOut(ch);

	//bmp=new Bitmap(w,h);
	Graphics* graph = Graphics::FromImage(&bmp);
	graph->Clear(Color(0,0,0));
	if(i==-1)
	{
		Pen pen(Color(255,255,255),2.0);
		graph->DrawLine(&pen,0,0,w,h);
		graph->DrawLine(&pen,0,h,w,0);
		graph->DrawString(L"No FFT Output",-1,new Font(TEXT_FONT,24),PointF(0.0,0.0),new SolidBrush(Color(255,0,0)));
		return ;
	}else
	{
		TCHAR buf[256];

		//const unsigned int count=(out[ch].e-out[ch].b)/2+1;
		const unsigned int N=(out[ch].e-out[ch].b);
		const unsigned int count=(MAX_FREQ<dataStore->GetSamplingRate()/2)?(N*MAX_FREQ/dataStore->GetSamplingRate()+1):
			dataStore->GetSamplingRate()/2;
		const int graphWidth=w-2*MARGIN_WIDTH;
		const int graphHeight=h-2*MARGIN_WIDTH;
		const double wScaler=1.0/count*graphWidth;
		const double hScaler=1.0/out[ch].max*graphHeight*MAX_SCALER;

		FontFamily   fontFamily(TEXT_FONT);
		//-------------------draw graph-----------------------------------------------	
		for(unsigned int i=1;i<count;i++){
			double x=MARGIN_WIDTH+i*wScaler;
			double y=MARGIN_WIDTH+graphHeight-out[ch].abs[i]*hScaler;

			bool isWhite=1;
			double r,g,b;
			double f=double(i)*MAX_FREQ/count;
			if(f>=0 && f<componentDef[0]) r=51,g=198,b=99,isWhite=0;
			if(f>=componentDef[1] && f<componentDef[2]) r=27,g=100,b=22,isWhite=0;
			if(f>=componentDef[3] && f<componentDef[4]) r=150,g=232,b=150,isWhite=0;
			if(f>=componentDef[5] && f<componentDef[6]) r=226,g=119,b=22,isWhite=0;
			if(f>=componentDef[7] && f<componentDef[8]) r=150,g=20,b=200,isWhite=0;
			if(f>=componentDef[9]) r=255,g=248,b=96,isWhite=0;
			if(isWhite) r=255,g=255,b=255;



			SolidBrush sb(Color(r,g,b));

			graph->FillRectangle(&sb,RectF(x,y,ceil(wScaler),out[ch].abs[i]*hScaler));
		}
		//----------------------------------------------------------------------------------------

		graph->DrawRectangle(new Pen(Color(255,255,255),AXIS_WIDTH),
			RectF(MARGIN_WIDTH,MARGIN_WIDTH,graphWidth,graphHeight));//draw axis

		const double freq_seg=MAX_FREQ/AXIS_SEGMENT;
		Font         font1(&fontFamily, AXIS_TEXT_SIZE, FontStyleBold, UnitPoint);
		SolidBrush   sb1(Color(255,255,255));

		for(int i=0;i<AXIS_SEGMENT;i++){
			int x=MARGIN_WIDTH+graphWidth/AXIS_SEGMENT*i;
			int y=MARGIN_WIDTH+graphHeight;
			graph->DrawLine(new Pen(Color(255,255,255)),x,y,x,y-15);
			//_itow((int)(freq_seg*i),buf,10);
			swprintf(buf,256,L"%4.2f",freq_seg*i);
			graph->DrawString(buf,-1,&font1,PointF(x-AXIS_TEXT_SIZE,y+5.0),&sb1);
		}

		const double cur_seg=out[ch].max*2/(out[ch].e-out[ch].b)/MAX_SCALER/AXIS_SEGMENT;
		for(int i=AXIS_SEGMENT;i>0;i--){
			int x=MARGIN_WIDTH;
			int y=MARGIN_WIDTH+graphHeight/AXIS_SEGMENT*i;
			graph->DrawLine(new Pen(Color(255,255,255)),x,y,x+15,y);
			swprintf(buf,256,L"%4.2f",cur_seg*(AXIS_SEGMENT-i));
			graph->DrawString(buf,-1,&font1,PointF(x-MARGIN_WIDTH+5.0,y-AXIS_TEXT_SIZE),&sb1);
		}
		//CLSID   encoderClsid;
		//GetEncoderClsid(L"image/bmp", &encoderClsid);
		//bmp->Save(L"Lddd.bmp",&encoderClsid);

		swprintf(buf,256,L"FFT Graph @ Ch. %d(\"%s\" eid=%d)",ch,
			dataStore->GetChannel(out[ch].channel).electrode.name,dataStore->GetChannel(out[ch].channel).electrodeID);
		Font         font(&fontFamily, TITLE_SIZE, FontStyleBold, UnitPoint);
		RectF        rTitle(0.0f, 0.0f, w, MARGIN_WIDTH);
		StringFormat stringFormat;
		SolidBrush   sb2(Color( 255, 255, 0));
		stringFormat.SetAlignment(StringAlignmentCenter);
		graph->DrawString(buf, -1, &font, rTitle, &stringFormat, &sb2);//draw title

		SolidBrush   sb3(Color( 0, 255, 255));
		Font         font2(&fontFamily, TEXT_SIZE, FontStyleBold, UnitPoint);
		graph->DrawString(L"Cur.",-1,&font2,PointF(5.0,MARGIN_WIDTH-5.0-TEXT_SIZE),&sb3);
		graph->DrawString(L"Freq.",-1,&font2,PointF(MARGIN_WIDTH+graphWidth-TEXT_SIZE ,MARGIN_WIDTH+5.0+graphHeight),&sb3);

		Font         font3(&fontFamily, DATE_TEXT_SIZE, FontStyleBold, UnitPoint);
		stringFormat.SetAlignment(StringAlignmentFar);
		graph->DrawString(getTimeString(),-1,&font3,RectF(0.0,h-2*DATE_TEXT_SIZE,w,2*DATE_TEXT_SIZE),&stringFormat,new SolidBrush(Color(200,200,200)));


	}
	SAFE_DELETE(graph);
}

int GSpectForm::selectChannel(int x, int y)
{
	RECT r;
	int ret=-1;
	parent->GetClientRect(&r);

	const double gw=r.right-3.0*MARGIN_WIDTH;
	const double gh=(r.bottom-2.0*MARGIN_WIDTH)/FFT_GRAPH_PER_PAGE;

	for(int i=0;i<FFT_GRAPH_PER_PAGE;i++){
		if(x>MARGIN_WIDTH && x<(MARGIN_WIDTH+gw) && (y>MARGIN_WIDTH+gh*i) && (y<MARGIN_WIDTH+gh*(i+1)))
		{
			ret=currentPage*FFT_GRAPH_PER_PAGE+i;
			break;
		}
	}
	if(FindInOut(ret)==-1)
		ret=-1;

	selectedChannel=ret;
	return ret;
}

void GSpectForm::DrawAllOutput(Bitmap &bmp,int w,int h)
{
	TCHAR buf[256];
	FontFamily   fontFamily(TEXT_FONT);
	StringFormat stringFormat;
	Graphics* graph = Graphics::FromImage(&bmp);
	graph->Clear(Color(0,0,0));
	const double gw=w-3.0*MARGIN_WIDTH;
	const double gh=(h-2.0*MARGIN_WIDTH)/FFT_GRAPH_PER_PAGE;

	for(int i=0;i<FFT_GRAPH_PER_PAGE;i++){
		int ch=currentPage*FFT_GRAPH_PER_PAGE+i;
		int index=FindInBuf(ch);
		if(index==-1){
			BMPBuffer tmp;
			tmp.ch=ch;
			tmp.bmp=new Bitmap(gw,gh);
			DrawThumbnail(ch,tmp.bmp,gw,gh);
			bmpBuf.push_back(tmp);
			index=bmpBuf.size()-1;
		}
		graph->DrawImage(bmpBuf[index].bmp,
			RectF(MARGIN_WIDTH,MARGIN_WIDTH+gh*i,gw,gh));
		if(FindInOut(ch)==-1){
			swprintf(buf,256,L"None");
		}else{
			swprintf(buf,256,L"%s\neid=%d",dataStore->GetChannel(ch).electrode.name,out[ch].eid);
		}
		Font         font1(&fontFamily, AXIS_TEXT_SIZE, FontStyleBold, UnitPoint);
		graph->DrawString(buf,-1,&font1,PointF(5.0,MARGIN_WIDTH+gh*i+5.0),new SolidBrush(Color(255,255,255)));

	}

	for(int i=0;i<FFT_GRAPH_PER_PAGE;i++){
		graph->DrawRectangle(new Pen(Color(255,255,255),AXIS_WIDTH),
			RectF(MARGIN_WIDTH,MARGIN_WIDTH+gh*i,gw,gh));
	}

	if(selectedChannel!=-1){
		graph->DrawRectangle(new Pen(Color(255,255,0),AXIS_WIDTH),
			RectF(MARGIN_WIDTH,MARGIN_WIDTH+gh*(selectedChannel-FFT_GRAPH_PER_PAGE*currentPage),gw,gh));
	}


	SolidBrush   sb2(Color( 255, 255, 0));
	stringFormat.SetAlignment(StringAlignmentCenter);
	Font         font3(&fontFamily, DATE_TEXT_SIZE, FontStyleBold, UnitPoint);
	stringFormat.SetAlignment(StringAlignmentFar);
	graph->DrawString(getTimeString(),-1,&font3,
		RectF(0.0,h-2*DATE_TEXT_SIZE,w,2*DATE_TEXT_SIZE),&stringFormat,new SolidBrush(Color(200,200,200)));
	swprintf(buf,256,L"FFT Graph (%d/%d)" ,currentPage+1,
		(out.size()%FFT_GRAPH_PER_PAGE==0)?out.size()/FFT_GRAPH_PER_PAGE:out.size()/FFT_GRAPH_PER_PAGE+1
		);
	Font         font(&fontFamily, TITLE_SIZE, FontStyleBold, UnitPoint);
	RectF        rTitle(0.0f, 0.0f, w, MARGIN_WIDTH);
	stringFormat.SetAlignment(StringAlignmentCenter);
	graph->DrawString(buf, -1, &font, rTitle, &stringFormat, &sb2);//draw title

	//graph->DrawString(_T("Peak"),-1, &font3, 
	SAFE_DELETE(graph);
}

void GSpectForm::UpdateBitmapBuffer(void)
{
	for(size_t i=0;i<bmpBuf.size();i++)
		SAFE_DELETE(bmpBuf[i].bmp);
	bmpBuf.clear();
}

int GSpectForm::FindInBuf(int ch)
{
	int ret=-1;
	for(int i=0;i<bmpBuf.size();i++){
		if(bmpBuf[i].ch==ch){
			ret=i;break;
		}
	}
	return ret;
}

int GSpectForm::FindInOut(int ch)
{
	int ret=-1;
	for(int i=0;i<out.size();i++){
		if(out[i].channel==ch){
			ret=i;break;
		}
	}
	return ret;
}

void GSpectForm::DrawThumbnail(int ch,Bitmap *bmp,int w,int h)
{
	Graphics* graph = Graphics::FromImage(bmp);
	graph->Clear(Color(0,0,0));

	int index=FindInOut(ch);
	if(index==-1){
		Pen pen(Color(255,255,255),2.0);
		graph->FillRectangle(new SolidBrush(Color(128,128,128)),0,0,w,h);
		graph->DrawString(L"No FFT Output",-1,new Font(TEXT_FONT,12),PointF(0.0,0.0),
			new SolidBrush(Color(255,0,0)));
		SAFE_DELETE(graph);
		return ;
	}
	double max=out[0].max;
	for(int i=0;i<out.size();i++)
		max=(max<out[i].max)?out[i].max:max;
	const unsigned int N=(out[index].e-out[index].b);
	const unsigned int count=(MAX_FREQ<dataStore->GetSamplingRate()/2)?(N*MAX_FREQ/dataStore->GetSamplingRate()+1):
			dataStore->GetSamplingRate()/2;
	const double wScaler=1.0/count*w;
	const double hScaler=1.0/max*h*0.95;

	//FontFamily   fontFamily(TEXT_FONT);
	//-------------------draw graph-----------------------------------------------	
	for(unsigned int i=1;i<count;i++){
		double x=i*wScaler;
		double y=h-out[index].abs[i]*hScaler;
		double r,g,b;
		HSLtoRGB(240*out[index].abs[i]/out[index].max,240,120,&r,&g,&b);
		SolidBrush sb(Color(r,g,b));
		graph->FillRectangle(&sb,RectF(x,y,ceil(wScaler),out[ch].abs[i]*hScaler));
	}
	SAFE_DELETE(graph);
}

bool GSpectForm::ExportSelected2File(LPCTSTR filename)
{
	wofstream ofs(filename);
	if(!ofs)
		return false;

	ofs<<L"Channel_id,"<<selectedChannel<<endl;
	ofs<<L"Channel_name,"<<dataStore->GetChannel(selectedChannel).electrode.name<<endl;
	ofs<<L"Electrode_id,"<<dataStore->GetChannel(selectedChannel).electrodeID<<endl;
	ofs<<endl;
	ofs<<L"Points,"<<out[selectedChannel].e-out[selectedChannel].b<<endl;
	ofs<<L"Max.,"<<out[selectedChannel].max<<endl;
	ofs<<L"Mean.,"<<out[selectedChannel].mean<<endl;
	ofs<<endl;
	ofs<<L"real,imag,module"<<endl;
	const int S=(out[selectedChannel].e-out[selectedChannel].b)/2+1;
	for(size_t i=0;i<S;i++)
		ofs<<out[selectedChannel].out[i].real()<<L","<<out[selectedChannel].out[i].imag()
		<<L","<<out[selectedChannel].abs[i]<<endl;
	ofs.close();
	return true;
}

void GSpectForm::GetOutputDetailString(int ch, LPTSTR buf)
{
	const Channel &c = dataStore->GetChannel(ch);
	_stprintf(buf, _T("details..."));
}

bool GSpectForm::SetCurrentPage(int offset)
{
	const int pageCount = (out.size()%FFT_GRAPH_PER_PAGE==0)?out.size()/FFT_GRAPH_PER_PAGE:out.size()/FFT_GRAPH_PER_PAGE+1;
	if( (currentPage + offset ) < 0 || (currentPage + offset)>= pageCount){
		AfxMessageBox(_T("Invalid Page Number."));
		return false;
	}else
	{
		currentPage+=offset;
		//RedrawWindow(
		parent->Invalidate();
		parent->UpdateWindow();
	}
	return false;
}
